/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * Path Searcher Class
 *      Provides a mechanism for recursively searching directories for files.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Threading;
using System.Collections.Specialized;

namespace Adaptive.Foundation.IO
{
    /// <summary>
    /// Provides a mechanism for recursively searching directories for files.
    /// </summary>
    public class PathSearcher : DisposableObjectBase 
    {
        /*--------------------------------------------------------------------------------
         * Public Event Declarations
         *------------------------------------------------------------------------------*/
        #region Public Event Declarations
        /// <summary>
        /// Occurs when a new search is started.
        /// </summary>
        public event EventHandler SearchStart;
        /// <summary>
        /// Occurs when a search is cancelled.
        /// </summary>
        public event EventHandler SearchAborted;
        /// <summary>
        /// Occurs when a search completes.
        /// </summary>
        public event EventHandler SearchCompleted;
        /// <summary>
        /// Occurs during file search to update a UI.
        /// </summary>
        public event ProgressEventHandler ProgressUpdate;
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations

        private StringCollection _directoryResults;
        private StringCollection _fileResults;
        private Thread _executionThread;
        private bool _executeOperation;
        private bool _cancelled;
        private string _startPath = string.Empty;
        private string _dirSpec = "*.";
        private StringCollection _fileSpec;

        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <value>
        /// This is the default constructor.
        /// </value>
        public PathSearcher()
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _fileSpec = new StringCollection();
            _fileSpec.Add("*.*");
        }
        /// <summary>
        /// Initializes a new instance of the class for the specified start path.
        /// </summary>
        /// <param name="startPath">
        /// The path in which to begin searching.
        /// </param>
        public PathSearcher(string startPath)
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _startPath = startPath;
            _fileSpec = new StringCollection();
            _fileSpec.Add("*.*");
        }
        /// <summary>
        /// Initializes a new instance of the class for the specified start path and
        /// file specification.
        /// </summary>
        /// <param name="startPath">
        /// The path in which to begin searching.
        /// </param>
        /// <param name="fileSpec">
        /// The file specification to use.
        /// </param>
        public PathSearcher(string startPath, string fileSpec)
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _startPath = startPath;
            _fileSpec = new StringCollection();
            _fileSpec.Add(fileSpec);
        }
        /// <summary>
        /// Initializes a new instance of the class for the specified start path, directory specification, and
        /// file specification.
        /// </summary>
        /// <param name="startPath">
        /// The path in which to begin searching.
        /// </param>
        /// <param name="directorySpec">
        /// The directory specification used to find subdirectories.
        /// </param>
        /// <param name="fileSpec">
        /// The file specification to use.
        /// </param>
        public PathSearcher(string startPath, string directorySpec, string fileSpec)
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _startPath = startPath;
            _dirSpec = directorySpec;
            _fileSpec = new StringCollection();
            _fileSpec.Add(fileSpec);
        }
        /// <summary>
        /// Initializes a new instance of the class for the specified start path and
        /// file specifications.
        /// </summary>
        /// <param name="startPath">
        /// The path in which to begin searching.
        /// </param>
        /// <param name="fileSpec">
        /// A string array containing the file specifications to use.
        /// </param>
        public PathSearcher(string startPath, string[] fileSpec)
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _startPath = startPath;
            _fileSpec = new StringCollection();
            _fileSpec.AddRange(fileSpec);
        }
        /// <summary>
        /// Initializes a new instance of the class for the specified start path, directory spec, and
        /// file specifications.
        /// </summary>
        /// <param name="startPath">
        /// The path in which to begin searching.
        /// </param>
        /// <param name="directorySpec">
        /// The directory specification used to find subdirectories.
        /// </param>
        /// <param name="fileSpec">
        /// A string array containing the file specifications to use.
        /// </param>
        public PathSearcher(string startPath, string directorySpec, string[] fileSpec)
        {
            _directoryResults = new StringCollection();
            _fileResults = new StringCollection();
            _startPath = startPath;
            _dirSpec = directorySpec;
            _fileSpec = new StringCollection();
            _fileSpec.AddRange(fileSpec);
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_directoryResults != null)
                    _directoryResults.Clear();
                if (_fileResults != null)
                    _fileResults.Clear();
            }
            _dirSpec = null;
            _fileSpec = null;
            _startPath = null;
            _fileResults = null;
            _executionThread = null;
            _directoryResults = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets the reference to the list of sub-directory names that were found.
        /// </summary>
        /// <value>
        /// A <see cref="StringCollection"/> instance.
        /// </value>
        public StringCollection DirectoryResults
        {
            get{return _directoryResults;}
        }
        /// <summary>
        /// Gets the reference to the list of file names that were found.
        /// </summary>
        /// <value>
        /// A <see cref="StringCollection"/> instance.
        /// </value>
        public StringCollection FileResults
        {
            get{return _fileResults;}
        }
        /// <summary>
        /// Gets a value indicating wheter the current search has been cancelled.
        /// </summary>
        /// <value>
        /// <b>true</b> if the search was cancelled; otherwise, <b>false</b>.
        /// </value>
        public bool Cancelled
        {
            get{return _cancelled;}
        }
        /// <summary>
        /// Gets or sets the starting path in which to begin a search.
        /// </summary>
        /// <value>
        /// A valid Win32 path.
        /// </value>
        public string StartPath
        {
            get{return _startPath;}
            set
            {
                if (!_executeOperation)
                    _startPath=value;
            }
        }
        /// <summary>
        /// Gets or sets the specification used to limit the 
        /// sub-directory results.
        /// </summary>
        /// <value>
        /// A directory name specification.
        /// </value>
        public string DirectorySpec
        {
            get{return _dirSpec;}
            set
            {
                if (!_executeOperation)
                    _dirSpec = value;
            }
        }
        /// <summary>
        /// Gets the reference to the list of file specifications used
        /// in the search.
        /// </summary>
        /// <value>
        /// A <see cref="StringCollection"/> instance.
        /// </value>
        public StringCollection FileSpecs
        {
            get
            {
                return _fileSpec;
            }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Protected Methods / Functions
        /// <summary>
        /// Raises the <see cref="SearchStart"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments object.
        /// </param>
        protected virtual void OnSearchStart(EventArgs e)
        {
            if (SearchStart != null)
                SearchStart(this, e);
        }
        /// <summary>
        /// Raises the <see cref="SearchAborted"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments object.
        /// </param>
        protected virtual void OnSearchAborted(EventArgs e)
        {
            if (SearchAborted != null)
                SearchAborted(this, e);
        }
        /// <summary>
        /// Raises the <see cref="SearchCompleted"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments object.
        /// </param>
        protected virtual void OnSearchCompleted(EventArgs e)
        {
            if (SearchCompleted != null)
                SearchCompleted(this, e);
        }
        /// <summary>
        /// Raises the <see cref="ProgressUpdate"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="ProgressEventArgs"/> event arguments object.
        /// </param>
        protected virtual void OnProgressUpdate(ProgressEventArgs e)
        {
            if (ProgressUpdate != null)
                ProgressUpdate(this, e);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Begins an asynchronous search.
        /// </summary>
        public void BeginSearch()
        {
            _executeOperation = true;
            _executionThread = new Thread(new ThreadStart(Search));
            _executionThread.IsBackground = true;
            _executionThread.Start();
        }
        /// <summary>
        /// Cancels the current search operation.
        /// </summary>
        public void CancelSearch()
        {
            _cancelled = true;
        }
        /// <summary>
        /// Waits for the current asynchronous search to complete.
        /// </summary>
        public void EndSearch()
        {
            while ((_executionThread != null) && (_executionThread.IsAlive))
                Thread.Sleep(100);

            _executionThread = null;
            _executeOperation = false;
        }
        /// <summary>
        /// Searches for files as specified by the property definitions.
        /// </summary>
        public void Search()
        {
            _executeOperation = true;
            _fileResults.Clear();
            _directoryResults.Clear();
            _cancelled = false;

            OnSearchStart(EventArgs.Empty);
            _directoryResults.Add(_startPath);
            SearchForPaths(_startPath, _dirSpec);

            if (!_cancelled)
                SearchForFiles();

            if (!_cancelled)
                OnSearchCompleted(EventArgs.Empty);
            else
                OnSearchAborted(EventArgs.Empty);

            _executeOperation = false;
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Locates all the paths being searched for.
        /// </summary>
        /// <param name="path">The path to be searched.</param>
        /// <param name="dirSpec">The specification to use.</param>
        private void SearchForPaths(string path, string dirSpec)
        {
            string[] results  = null;

            if (!_cancelled)
            {
                try
                {
                    results = Directory.GetDirectories(path, dirSpec);
                }
                catch
                {
                    results = null;
                }

                if (results != null)
                {
                    _directoryResults.AddRange(results);
                    foreach (string subDir in results)
                        SearchForPaths(subDir, dirSpec);

                    Array.Clear(results, 0, results.Length);
                }
                results = null;
            }
        }
        /// <summary>
        /// Searches for the specified files.
        /// </summary>
        private void SearchForFiles()
        {
            ProgressEventArgs evArgs = null;
            int count = 0;
            int length = _directoryResults.Count;
            string[] files = null;

            if (!_cancelled)
            {
                evArgs = new ProgressEventArgs();
                do
                {
                    foreach (string filespec in _fileSpec)
                    {
                        try
                        {
                            files = Directory.GetFiles(_directoryResults[count], filespec);
                        }
                        catch
                        {
                            files = null;
                        }
                        if (files != null)
                        {
                            _fileResults.AddRange(files);
                            Array.Clear(files, 0, files.Length);
                        }
                    }
                    count++;
                    if (count % 5 == 0)
                    {
                        if (count < length)
                        {
                            evArgs.PercentComplete = (int)(((float)count / length) * 100);
                            evArgs.Status = _directoryResults[count];
                            OnProgressUpdate(evArgs);
                        }
                    }
                } while ((!_cancelled) && (count < length));
                evArgs = null;
            }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Event Handlers
         *------------------------------------------------------------------------------*/
        #region Private Event Handlers
        #endregion
    }
}